/*******************************************************************************
 *     ___                  _   ____  ____
 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
 *   | | | | | | |/ _ \/ __| __| | | |  _ \
 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
 *    \__\_\\__,_|\___||___/\__|____/|____/
 *
 *  Copyright (c) 2014-2019 Appsicle
 *  Copyright (c) 2019-2024 QuestDB
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

package io.questdb.griffin.engine.functions.regex;

import io.questdb.cairo.CairoConfiguration;
import io.questdb.cairo.sql.Function;
import io.questdb.griffin.FunctionFactory;
import io.questdb.griffin.SqlException;
import io.questdb.griffin.SqlExecutionContext;
import io.questdb.griffin.engine.functions.constants.StrConstant;
import io.questdb.std.Chars;
import io.questdb.std.Files;
import io.questdb.std.IntList;
import io.questdb.std.ObjList;
import io.questdb.std.ThreadLocal;
import io.questdb.std.str.StringSink;
import org.jetbrains.annotations.NotNull;

public class GlobStrFunctionFactory implements FunctionFactory {
    private static final io.questdb.std.ThreadLocal<StringSink> tlSink = new ThreadLocal<>(StringSink::new);
    private final MatchStrFunctionFactory matchStrFactory = new MatchStrFunctionFactory();

    public static void convertGlobPatternToRegex(@NotNull CharSequence globPattern, StringSink sink, int position) throws SqlException {
        int bracketStackDepth = 0;
        sink.put('^'); // start anchor
        for (int i = 0, n = globPattern.length(); i < n; i++) {
            char c = globPattern.charAt(i);
            switch (c) {
                case '.', '^', '$', '+', '{', '}', '(', ')', '|' -> {
                    sink.put('\\');
                    sink.put(c);
                }
                case '\\' -> sink.put("\\\\");
                case '*' -> {
                    if (i + 1 < n && globPattern.charAt(i + 1) == '*' && i + 2 < n && globPattern.charAt(i + 2) == Files.SEPARATOR) {
                        i++;
                    }
                    sink.put(".*");
                }
                case '?' -> sink.put('.');
                case '[' -> {
                    bracketStackDepth++;
                    sink.put('[');
                }
                case ']' -> {
                    bracketStackDepth--;
                    sink.put(']');
                }
                case '!' -> {
                    if (bracketStackDepth > 0) {
                        sink.put('^');
                    } else {
                        sink.put('!');
                    }
                }
                default -> sink.put(c);
            }
        }
        sink.put('$'); // end anchor
        if (bracketStackDepth != 0) {
            throw SqlException.$(position, "unbalanced bracket [glob=").put(globPattern).put(']');
        }
    }

    @Override
    public String getSignature() {
        return "glob(Ss)";
    }

    @Override
    public Function newInstance(int position, ObjList<Function> args, IntList argPositions, CairoConfiguration configuration, SqlExecutionContext sqlExecutionContext) throws SqlException {
        final Function arg = args.getQuick(1);
        assert arg.isConstant();
        final CharSequence globPattern = arg.getStrA(null);

        if (Chars.isBlank(globPattern)) {
            throw SqlException.$(argPositions.get(1), "glob pattern must not be null or empty");
        }

        final StringSink sink = tlSink.get();
        sink.clear();
        convertGlobPatternToRegex(globPattern, sink, argPositions.get(1));
        StrConstant regex = StrConstant.newInstance(sink);

        final ObjList<Function> newArgList = args.copy();
        newArgList.set(1, regex);
        return matchStrFactory.newInstance(position, newArgList, argPositions, configuration, sqlExecutionContext);
    }
}